#include <cstddef>
#include <cassert>
#include <stlsoft/synch/lock_scope.hpp>
#include <winstl/synch/thread_mutex.hpp>
#include <winstl/synch/atomic_types.h>
#include <winstl/synch/atomic_functions.h>
#include <windows.h>
#include <process.h>
#include "thread_pool.hpp"
#include "exception.hpp"

using namespace std;
using stlsoft::lock_scope;
using winstl::thread_mutex;

namespace judge {

thread_pool::thread_pool()
	: queue_handle_(_create_queue())
{
	try {
		threads_.push_back(_create_thread());
	} catch (...) {
		_destroy();
		throw;
	}
}

thread_pool::~thread_pool()
{
	_destroy();
}

void thread_pool::_destroy()
{
	BOOL result = ::PostQueuedCompletionStatus(queue_handle_, 0, 0, NULL);
	if (!result) {
		throw win32_exception(::GetLastError());
	}

	while (1) {
		HANDLE thread = NULL;
		{
			lock_scope<thread_mutex> lock(threads_mutex_);
			if (!threads_.empty()) {
				thread = threads_.front();
			}
		}
		if (!thread)
			break;
		DWORD wait_result = ::WaitForSingleObject(thread, INFINITE);
		if (wait_result == WAIT_FAILED) {
			throw win32_exception(::GetLastError());
		}
		{
			lock_scope<thread_mutex> lock(threads_mutex_);
			assert(!threads_.empty());
			threads_.front() = move(threads_.back());
			threads_.pop_back();
		}
	}

	::CloseHandle(queue_handle_);
}

HANDLE thread_pool::_create_queue()
{
	HANDLE result = reinterpret_cast<HANDLE>(
		::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0));
	if (!result) {
		throw win32_exception(::GetLastError());
	}
	return result;
}

HANDLE thread_pool::_create_thread()
{
	HANDLE result = reinterpret_cast<HANDLE>(
		::_beginthreadex(nullptr, 0, _thread_entry, this, 0, nullptr));
	if (!result) {
		throw judge_exception(JSTATUS_GENERIC_ERROR);
	}
	return result;
}

unsigned int __stdcall thread_pool::_thread_entry(void *param)
{
	thread_pool &tp = *reinterpret_cast<thread_pool *>(param);
	DWORD size;
	ULONG_PTR key;
	LPOVERLAPPED overlapped;
	bool first = true;

	while (::GetQueuedCompletionStatus(tp.queue_handle_, &size, &key, &overlapped, INFINITE)) {
		if (!overlapped) {
			BOOL result = ::PostQueuedCompletionStatus(tp.queue_handle_, 0, 0, NULL);
			if (!result) {
				throw win32_exception(::GetLastError());
			}
			return 0;
		}

		unique_ptr<context_base> context(reinterpret_cast<context_base *>(overlapped));

		if (first) {
			try {
				lock_scope<thread_mutex> lock(tp.threads_mutex_);
				tp.threads_.push_back(tp._create_thread());
				first = false;
			} catch (...) {
			}
		}

		try {
			context->invoke();
		} catch (...) {
			assert(!"uncaught exception");
		}
	}

	return 1;
}

}
